#include "ibm.h"
#include "nvr.h"
#include "vid_ati_eeprom.h"

enum { EEPROM_IDLE, EEPROM_WAIT, EEPROM_OPCODE, EEPROM_INPUT, EEPROM_OUTPUT };

enum {
        EEPROM_OP_EW = 4,
        EEPROM_OP_WRITE = 5,
        EEPROM_OP_READ = 6,
        EEPROM_OP_ERASE = 7,

        EEPROM_OP_WRALMAIN = -1
};

enum { EEPROM_OP_EWDS = 0, EEPROM_OP_WRAL = 1, EEPROM_OP_ERAL = 2, EEPROM_OP_EWEN = 3 };

void ati_eeprom_load(ati_eeprom_t *eeprom, char *fn, int type) {
        FILE *f;
        eeprom->type = type;
        strcpy(eeprom->fn, fn);
        f = nvrfopen(eeprom->fn, "rb");
        if (!f) {
                memset(eeprom->data, 0xff, eeprom->type ? 512 : 128);
                return;
        }
        fread(eeprom->data, 1, eeprom->type ? 512 : 128, f);
        fclose(f);
}

void ati_eeprom_save(ati_eeprom_t *eeprom) {
        FILE *f = nvrfopen(eeprom->fn, "wb");
        if (!f)
                return;
        fwrite(eeprom->data, 1, eeprom->type ? 512 : 128, f);
        fclose(f);
}

void ati_eeprom_write(ati_eeprom_t *eeprom, int ena, int clk, int dat) {
        int c;
        //        pclog("EEPROM write %i %i %i\n", ena, clk, dat);
        if (!ena) {
                eeprom->out = 1;
        }
        if (clk && !eeprom->oldclk) {
                if (ena && !eeprom->oldena) {
                        eeprom->state = EEPROM_WAIT;
                        eeprom->opcode = 0;
                        eeprom->count = 3;
                        eeprom->out = 1;
                } else if (ena) {
                        //                        pclog("EEPROM receive %i %i %i\n", ena, clk, dat);
                        switch (eeprom->state) {
                        case EEPROM_WAIT:
                                if (!dat)
                                        break;
                                eeprom->state = EEPROM_OPCODE;
                                /* fall through */
                        case EEPROM_OPCODE:
                                eeprom->opcode = (eeprom->opcode << 1) | (dat ? 1 : 0);
                                eeprom->count--;
                                if (!eeprom->count) {
                                        //                                        pclog("EEPROM opcode - %i\n", eeprom->opcode);
                                        switch (eeprom->opcode) {
                                        case EEPROM_OP_WRITE:
                                                eeprom->count = eeprom->type ? 24 : 22;
                                                eeprom->state = EEPROM_INPUT;
                                                eeprom->dat = 0;
                                                break;
                                        case EEPROM_OP_READ:
                                                eeprom->count = eeprom->type ? 8 : 6;
                                                eeprom->state = EEPROM_INPUT;
                                                eeprom->dat = 0;
                                                break;
                                        case EEPROM_OP_EW:
                                                eeprom->count = 2;
                                                eeprom->state = EEPROM_INPUT;
                                                eeprom->dat = 0;
                                                break;
                                        case EEPROM_OP_ERASE:
                                                eeprom->count = eeprom->type ? 8 : 6;
                                                eeprom->state = EEPROM_INPUT;
                                                eeprom->dat = 0;
                                                break;
                                        }
                                }
                                break;

                        case EEPROM_INPUT:
                                eeprom->dat = (eeprom->dat << 1) | (dat ? 1 : 0);
                                eeprom->count--;
                                if (!eeprom->count) {
                                        //                                        pclog("EEPROM dat - %02X\n", eeprom->dat);
                                        switch (eeprom->opcode) {
                                        case EEPROM_OP_WRITE:
                                                //                                                pclog("EEPROM_OP_WRITE addr %02X
                                                //                                                eeprom_dat %04X\n", (eeprom->dat
                                                //                                                >> 16) & (eeprom->type ? 255 :
                                                //                                                63), eeprom->dat & 0xffff);
                                                if (!eeprom->wp) {
                                                        eeprom->data[(eeprom->dat >> 16) & (eeprom->type ? 255 : 63)] =
                                                                eeprom->dat & 0xffff;
                                                        ati_eeprom_save(eeprom);
                                                }
                                                eeprom->state = EEPROM_IDLE;
                                                eeprom->out = 1;
                                                break;

                                        case EEPROM_OP_READ:
                                                eeprom->count = 17;
                                                eeprom->state = EEPROM_OUTPUT;
                                                eeprom->dat = eeprom->data[eeprom->dat];
                                                //                                                pclog("Trigger EEPROM_OUTPUT
                                                //                                                %04X\n", eeprom->dat);
                                                break;
                                        case EEPROM_OP_EW:
                                                //                                                pclog("EEPROM_OP_EW %i\n",
                                                //                                                eeprom->dat);
                                                switch (eeprom->dat) {
                                                case EEPROM_OP_EWDS:
                                                        eeprom->wp = 1;
                                                        break;
                                                case EEPROM_OP_WRAL:
                                                        eeprom->opcode = EEPROM_OP_WRALMAIN;
                                                        eeprom->count = 20;
                                                        break;
                                                case EEPROM_OP_ERAL:
                                                        if (!eeprom->wp) {
                                                                memset(eeprom->data, 0xff, 128);
                                                                ati_eeprom_save(eeprom);
                                                        }
                                                        break;
                                                case EEPROM_OP_EWEN:
                                                        eeprom->wp = 0;
                                                        break;
                                                }
                                                eeprom->state = EEPROM_IDLE;
                                                eeprom->out = 1;
                                                break;

                                        case EEPROM_OP_ERASE:
                                                //                                                pclog("EEPROM_OP_ERASE %i\n",
                                                //                                                eeprom->dat);
                                                if (!eeprom->wp) {
                                                        eeprom->data[eeprom->dat] = 0xffff;
                                                        ati_eeprom_save(eeprom);
                                                }
                                                eeprom->state = EEPROM_IDLE;
                                                eeprom->out = 1;
                                                break;

                                        case EEPROM_OP_WRALMAIN:
                                                //                                                pclog("EEPROM_OP_WRAL %04X\n",
                                                //                                                eeprom->dat);
                                                if (!eeprom->wp) {
                                                        for (c = 0; c < 256; c++)
                                                                eeprom->data[c] = eeprom->dat;
                                                        ati_eeprom_save(eeprom);
                                                }
                                                eeprom->state = EEPROM_IDLE;
                                                eeprom->out = 1;
                                                break;
                                        }
                                }
                                break;
                        }
                }
                eeprom->oldena = ena;
        } else if (!clk && eeprom->oldclk) {
                if (ena) {
                        switch (eeprom->state) {
                        case EEPROM_OUTPUT:
                                eeprom->out = (eeprom->dat & 0x10000) ? 1 : 0;
                                eeprom->dat <<= 1;
                                //                                pclog("EEPROM_OUTPUT - data %i\n", eeprom->out);
                                eeprom->count--;
                                if (!eeprom->count) {
                                        //                                        pclog("EEPROM_OUTPUT complete\n");
                                        eeprom->state = EEPROM_IDLE;
                                }
                                break;
                        }
                }
        }
        eeprom->oldclk = clk;
}

int ati_eeprom_read(ati_eeprom_t *eeprom) { return eeprom->out; }
